package dk.au.cs.cwp.peacechat;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class PeaceChat
 */
@WebServlet(name = "PeaceChat", urlPatterns = "/PeaceChat", asyncSupported = true)
public class PeaceChat extends HttpServlet {

	private static final long serialVersionUID = 1L;
	private static final BlockingQueue<String> chatMessages = new LinkedBlockingQueue<String>();
	private static final Queue<AsyncContext> clients = new ConcurrentLinkedQueue<AsyncContext>();
	private Thread notifierThread = null;

	public void init(ServletConfig config) {
		//Creating new runnable that will run in separate thread
		Runnable notifier = new Runnable() {
			@Override
			public void run() {
				boolean done = false;
				while (!done) {
					try {
						String message = chatMessages.take(); // blocking call, waits until there are a new message
						for (AsyncContext con : clients) {
							//Looping through all listening clients
							//Ensuring that UTF-8 is set as encoding
							con.getResponse().setCharacterEncoding("UTF-8");
							try {
								//Printing message to stream
								PrintWriter writer = con.getResponse()
										.getWriter();
								writer.print(message);
								//Done writing, flushing and calling complete to close down connection
								writer.flush();
								con.complete();
							} catch (IOException e) {
								e.printStackTrace();
								//Something went wrong, remove client from the list of listeners
								clients.remove(con);
							}
						}
					} catch (InterruptedException e) {
						//If thread is interrupted we shut down nicely
						done = true;
					}
				}
			}
		};
		notifierThread = new Thread(notifier);
		//Start the new thread
		notifierThread.start();
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		// A client wants updates
		final AsyncContext con = request.startAsync();
		//Timeout is 10 minutes
		con.setTimeout(1000*60*10);
		//We add a listener so that the client is removed whenever something happens to the connection
		con.addListener(new AsyncListener() {
			@Override
			public void onComplete(AsyncEvent event) throws IOException {
				clients.remove(con);
			}

			@Override
			public void onTimeout(AsyncEvent event) throws IOException {
				clients.remove(con);
			}

			@Override
			public void onError(AsyncEvent event) throws IOException {
				clients.remove(con);
			}

			@Override
			public void onStartAsync(AsyncEvent event) throws IOException {
				//Do nothing here :D
			}
		});
		//Add the new asynchronous connection to the queue of listeners
		clients.add(con);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		//Get nick and message from parameters
		String nick = request.getParameter("nick");
		String message = request.getParameter("message");
		if (nick != null && message != null) {
			//We only add the message if both nick and message is in the parameters
			String chatMessage = "{\"name\":\"" + nick + "\","
					+ "\"message\":\"" + message + "\"}";
				try {
					//Add the message to the queue of messages
					chatMessages.put(chatMessage);
				} catch (InterruptedException e) {
					//Something went wrong, we wrap the exception and throws a new IOException
					IOException t = new IOException();
					t.initCause(e);
					throw t;
				}
		} else {
			//Some parameter was not set so we do nothing
		}
	}
}